前置知识: Lua

Lua与World of Warcraft

00:00
7 min Intermediate 2026/6/14

WoW插件开发

概述

World of Warcraft(魔兽世界,简称 WoW)是一款广泛使用 Lua 作为插件脚本语言的大型多人在线角色扮演游戏。WoW 的插件系统允许玩家使用 Lua 编写自定义界面和功能扩展,从简单的信息显示到复杂的团队管理工具,都可以通过 Lua 插件实现。WoW 的 UI 框架基于 XML 布局和 Lua 逻辑的结合,开发者使用 XML 定义界面结构,使用 Lua 编写交互逻辑。

WoW 插件生态非常活跃,CurseForge 等平台上托管了数以万计的插件。学习 WoW 插件开发不仅能够定制自己的游戏体验,也是 Lua 实际应用的一个极佳案例。WoW 中的 Lua 运行在受限沙箱环境中,去除了文件 I/O、网络访问等危险 API,但提供了丰富的游戏 API 用于获取游戏状态、创建界面元素、注册事件处理等。

基本概念

TOC 文件是插件的描述文件,全称为 Table of Contents。每个插件必须包含一个 .toc 文件,其中声明了插件的名称、版本、接口版本、依赖关系以及加载的文件列表。WoW 客户端通过读取 TOC 文件来加载和初始化插件。

事件系统是 WoW 插件的核心机制。游戏中的各种状态变化(如玩家进入世界、受到伤害、目标改变等)都会触发对应的事件。插件通过注册事件监听器来响应这些事件,从而实现动态的交互逻辑。

**框架(Frame)**是 WoW UI 系统的基础元素。所有可见的界面组件(按钮、文本、图标等)都基于 Frame 创建。Frame 可以注册事件、设置脚本处理函数、包含子框架,是构建插件界面的核心对象。

SavedVariables 是 WoW 提供的持久化存储机制。插件可以在 TOC 文件中声明需要持久化的变量,WoW 会在玩家退出游戏时自动将这些变量保存到磁盘,在下次登录时自动加载。这使得插件可以保存用户的配置和状态。

Ace3 框架是 WoW 插件开发中最流行的第三方库,提供了一套模块化的开发框架,包括事件处理、数据库管理、配置界面、命令行解析等常用功能。使用 Ace3 可以大幅简化插件开发流程。

快速开始

创建一个最简单的 WoW 插件,需要两个文件:TOC 描述文件和 Lua 脚本文件。

首先创建插件目录和 TOC 文件 MyFirstAddon/MyFirstAddon.toc

## Title: My First Addon
## Notes: 我的第一个魔兽世界插件
## Author: MyName
## Interface: 100205
## Version: 1.0.0

MyFirstAddon.lua

其中 Interface 字段对应 WoW 的版本号,不同版本的客户端需要匹配对应的接口版本。

然后创建 Lua 脚本文件 MyFirstAddon/MyFirstAddon.lua

-- 注册事件监听
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")

-- 设置事件处理函数
frame:SetScript("OnEvent", function(self, event, ...)
    print("欢迎使用 My First Addon!")
    print("当前角色: " .. UnitName("player"))
    print("当前等级: " .. UnitLevel("player"))
end)

将插件文件夹放入 WoW 的插件目录 Interface/AddOns/ 下,重启游戏或输入 /reload 即可加载插件。登录后聊天窗口会显示欢迎信息。

详细用法

事件处理系统

事件是 WoW 插件与游戏交互的主要方式。以下示例展示了如何监听多种事件:

local frame = CreateFrame("Frame")

-- 注册多个事件
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("PLAYER_REGEN_DISABLED")  -- 进入战斗
frame:RegisterEvent("PLAYER_REGEN_ENABLED")   -- 离开战斗
frame:RegisterEvent("CHAT_MSG_WHISPER")        -- 收到密语
frame:RegisterEvent("UNIT_HEALTH")             -- 生命值变化

frame:SetScript("OnEvent", function(self, event, ...)
    if event == "PLAYER_ENTERING_WORLD" then
        local isLogin = ...
        if isLogin then
            print("欢迎回来," .. UnitName("player") .. "!")
        end
    elseif event == "PLAYER_REGEN_DISABLED" then
        print("进入战斗!")
    elseif event == "PLAYER_REGEN_ENABLED" then
        print("离开战斗。")
    elseif event == "CHAT_MSG_WHISPER" then
        local msg, sender = ...
        print("收到 " .. sender .. " 的密语: " .. msg)
    elseif event == "UNIT_HEALTH" then
        local unit = ...
        if unit == "player" then
            local health = UnitHealth("player")
            local maxHealth = UnitHealthMax("player")
            local percent = (health / maxHealth) * 100
            if percent < 30 then
                print("警告:生命值低于 30%!")
            end
        end
    end
end)

创建界面元素

使用 WoW 的 UI API 创建各种界面组件:

-- 创建一个可拖动的提示框
local function create_info_frame()
    -- 创建主框架
    local frame = CreateFrame("Frame", "MyInfoFrame", UIParent, "BackdropTemplate")
    frame:SetSize(250, 120)
    frame:SetPoint("CENTER")
    frame:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
        tile = true,
        tileSize = 32,
        edgeSize = 16,
        insets = { left = 4, right = 4, top = 4, bottom = 4 },
    })
    frame:SetBackdropColor(0, 0, 0, 0.8)
    frame:SetMovable(true)
    frame:EnableMouse(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetScript("OnDragStart", frame.StartMoving)
    frame:SetScript("OnDragStop", frame.StopMovingOrSizing)

    -- 创建标题文本
    local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOP", frame, "TOP", 0, -10)
    title:SetText("角色信息")

    -- 创建信息文本
    local info = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    info:SetPoint("TOP", title, "BOTTOM", 0, -8)
    info:SetWidth(220)

    -- 更新信息的函数
    local function update_info()
        local name = UnitName("player")
        local level = UnitLevel("player")
        local _, class = UnitClass("player")
        local health = UnitHealth("player")
        local maxHealth = UnitHealthMax("player")
        local power = UnitPower("player")
        local maxPower = UnitPowerMax("player")

        info:SetText(string.format(
            "姓名: %s\n等级: %d\n职业: %s\n生命: %d / %d\n能量: %d / %d",
            name, level, class, health, maxHealth, power, maxPower
        ))
    end

    -- 定时更新信息
    local timer = 0
    frame:SetScript("OnUpdate", function(self, elapsed)
        timer = timer + elapsed
        if timer >= 1.0 then  -- 每秒更新一次
            timer = 0
            update_info()
        end
    end)

    -- 创建关闭按钮
    local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)

    update_info()
    return frame
end

local info_frame = create_info_frame()

创建按钮与交互

创建可点击的按钮并绑定动作:

-- 创建一个功能按钮
local function create_action_button(parent, text, onClick)
    local button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
    button:SetSize(120, 30)
    button:SetText(text)
    button:SetScript("OnClick", onClick)

    -- 添加鼠标悬停提示
    button:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(text, 1, 1, 1)
        GameTooltip:Show()
    end)
    button:SetScript("OnLeave", function(self)
        GameTooltip:Hide()
    end)

    return button
end

-- 使用示例:创建一组操作按钮
local panel = CreateFrame("Frame", "MyActionPanel", UIParent, "BackdropTemplate")
panel:SetSize(200, 140)
panel:SetPoint("LEFT", UIParent, "LEFT", 20, 0)
panel:SetBackdrop({
    bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    tile = true, tileSize = 16, edgeSize = 12,
    insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
panel:SetBackdropColor(0, 0, 0, 0.7)
panel:SetMovable(true)
panel:EnableMouse(true)
panel:RegisterForDrag("LeftButton")
panel:SetScript("OnDragStart", panel.StartMoving)
panel:SetScript("OnDragStop", panel.StopMovingOrSizing)

-- 修理按钮
local repairBtn = create_action_button(panel, "自动修理", function()
    if CanMerchantRepair() then
        local cost = GetRepairAllCost()
        if cost > 0 then
            if GetMoney() >= cost then
                RepairAllItems()
                print("修理完成,花费: " .. GetCoinTextureString(cost))
            else
                print("金币不足,无法修理")
            end
        else
            print("装备无需修理")
        end
    end
end)
repairBtn:SetPoint("TOPLEFT", panel, "TOPLEFT", 15, -15)

-- 卖垃圾按钮
local sellBtn = create_action_button(panel, "出售垃圾", function()
    local total = 0
    for bag = 0, NUM_BAG_SLOTS do
        for slot = 1, GetContainerNumSlots(bag) do
            local _, _, quality, _, _, _, _, _, _, itemID = GetContainerItemInfo(bag, slot)
            if itemID and quality == 0 then  -- 灰色品质物品
                local sellPrice = select(11, GetItemInfo(itemID))
                if sellPrice and sellPrice > 0 then
                    total = total + sellPrice
                    UseContainerItem(bag, slot)
                end
            end
        end
    end
    if total > 0 then
        print("出售垃圾物品,获得: " .. GetCoinTextureString(total))
    else
        print("没有可出售的垃圾物品")
    end
end)
sellBtn:SetPoint("TOPLEFT", repairBtn, "BOTTOMLEFT", 0, -5)

斜杠命令

为插件注册自定义的斜杠命令,方便用户交互:

-- 注册斜杠命令
SLASH_MYADDON1 = "/myaddon"
SLASH_MYADDON2 = "/ma"

SlashCmdList["MYADDON"] = function(msg)
    -- 解析命令参数
    local command, arg = msg:match("^(%S*)%s*(.-)$")

    if command == "" or command == "help" then
        print("MyAddon 命令列表:")
        print("  /ma help - 显示帮助信息")
        print("  /ma show - 显示信息面板")
        print("  /ma hide - 隐藏信息面板")
        print("  /ma reset - 重置配置")
        print("  /ma config <key> <value> - 设置配置项")
    elseif command == "show" then
        if info_frame then info_frame:Show() end
    elseif command == "hide" then
        if info_frame then info_frame:Hide() end
    elseif command == "reset" then
        MyAddonDB = nil  -- 清除保存的变量
        print("配置已重置,请重新加载界面 (/reload)")
    elseif command == "config" then
        local key, value = arg:match("^(%S+)%s+(.+)$")
        if key and value then
            MyAddonDB = MyAddonDB or {}
            MyAddonDB[key] = value
            print("配置已更新: " .. key .. " = " .. value)
        else
            print("用法: /ma config <key> <value>")
        end
    else
        print("未知命令: " .. command .. ",输入 /ma help 查看帮助")
    end
end

SavedVariables 持久化存储

在 TOC 文件中声明需要保存的变量:

## SavedVariables: MyAddonDB

然后在 Lua 代码中使用:

-- 初始化保存的变量(首次使用时设置默认值)
MyAddonDB = MyAddonDB or {
    showPanel = true,
    panelPosition = { point = "CENTER", x = 0, y = 0 },
    alerts = {
        lowHealth = true,
        lowHealthThreshold = 30,
        whisperAlert = true,
    },
}

-- 读取配置
local function get_config(key)
    return MyAddonDB[key]
end

-- 更新配置
local function set_config(key, value)
    MyAddonDB[key] = value
    -- 配置变更后立即生效
    if key == "showPanel" then
        if value then
            info_frame:Show()
        else
            info_frame:Hide()
        end
    end
end

-- 恢复面板位置
local function restore_position()
    local pos = MyAddonDB.panelPosition
    if pos then
        info_frame:ClearAllPoints()
        info_frame:SetPoint(pos.point, UIParent, pos.point, pos.x, pos.y)
    end
end

-- 保存面板位置
local function save_position()
    local point, _, _, x, y = info_frame:GetPoint()
    MyAddonDB.panelPosition = { point = point, x = x, y = y }
end

-- 在拖动停止时保存位置
info_frame:SetScript("OnDragStop", function(self)
    self:StopMovingOrSizing()
    save_position()
end)

-- 加载时恢复位置
restore_position()

常见场景

伤害统计插件

实现一个简单的伤害统计功能:

-- 伤害统计模块
local DamageTracker = {}
DamageTracker.__index = DamageTracker

function DamageTracker.new()
    local self = setmetatable({}, DamageTracker)
    self.data = {}  -- 玩家名 -> 总伤害
    self.combatStartTime = nil
    self.inCombat = false
    return self
end

function DamageTracker:StartCombat()
    self.data = {}
    self.combatStartTime = GetTime()
    self.inCombat = true
end

function DamageTracker:EndCombat()
    self.inCombat = false
    self:PrintReport()
end

function DamageTracker:RecordDamage(source, amount)
    if not self.inCombat then return end
    self.data[source] = (self.data[source] or 0) + amount
end

function DamageTracker:PrintReport()
    if not self.combatStartTime then return end

    local duration = GetTime() - self.combatStartTime
    print("--- 伤害统计报告 ---")
    print(string.format("战斗时长: %.1f 秒", duration))

    -- 按伤害量排序
    local sorted = {}
    for name, damage in pairs(self.data) do
        sorted[#sorted + 1] = {name = name, damage = damage}
    end
    table.sort(sorted, function(a, b) return a.damage > b.damage end)

    -- 计算总伤害
    local totalDamage = 0
    for _, entry in ipairs(sorted) do
        totalDamage = totalDamage + entry.damage
    end

    -- 打印每个玩家的统计
    for i, entry in ipairs(sorted) do
        local dps = entry.damage / duration
        local percent = (entry.damage / totalDamage) * 100
        print(string.format("%d. %s - 伤害: %d (%.1f%%) DPS: %.0f",
            i, entry.name, entry.damage, percent, dps))
    end
end

-- 创建追踪器实例
local tracker = DamageTracker.new()

-- 注册战斗事件
local combatFrame = CreateFrame("Frame")
combatFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
combatFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
combatFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")

combatFrame:SetScript("OnEvent", function(self, event, ...)
    if event == "PLAYER_REGEN_DISABLED" then
        tracker:StartCombat()
    elseif event == "PLAYER_REGEN_ENABLED" then
        tracker:EndCombat()
    elseif event == "COMBAT_LOG_EVENT_UNFILTERED" then
        local _, subEvent, _, sourceGUID, sourceName = CombatLogGetCurrentEventInfo()
        if subEvent == "SPELL_DAMAGE" or subEvent == "SWING_DAMAGE" or subEvent == "RANGE_DAMAGE" then
            local amount = select(12, CombatLogGetCurrentEventInfo())
            if sourceName and amount and amount > 0 then
                tracker:RecordDamage(sourceName, amount)
            end
        end
    end
end)

自动接受邀请

实现自动接受好友和公会成员的组队邀请:

-- 自动接受邀请配置
MyAddonDB = MyAddonDB or {
    autoAcceptFriends = true,
    autoAcceptGuild = true,
    autoAcceptAll = false,
}

local inviteFrame = CreateFrame("Frame")
inviteFrame:RegisterEvent("PARTY_INVITE_REQUEST")

inviteFrame:SetScript("OnEvent", function(self, event, sender)
    -- 检查是否来自好友
    if MyAddonDB.autoAcceptFriends then
        for i = 1, C_FriendList.GetNumFriends() do
            local friendInfo = C_FriendList.GetFriendInfoByIndex(i)
            if friendInfo and friendInfo.name == sender then
                AcceptGroup()
                print("自动接受好友 " .. sender .. " 的邀请")
                return
            end
        end
    end

    -- 检查是否来自公会成员
    if MyAddonDB.autoAcceptGuild then
        if IsInGuild() then
            for i = 1, GetNumGuildMembers() do
                local name = GetGuildRosterInfo(i)
                if name and name:match(sender) then
                    AcceptGroup()
                    print("自动接受公会成员 " .. sender .. " 的邀请")
                    return
                end
            end
        end
    end

    -- 全部自动接受
    if MyAddonDB.autoAcceptAll then
        AcceptGroup()
        print("自动接受 " .. sender .. " 的邀请")
        return
    end

    -- 未自动接受,提示用户
    print("收到 " .. sender .. " 的组队邀请")
end)

背包整理

实现一键整理背包的功能:

-- 背包整理模块
local BagSorter = {}

function BagSorter.Sort()
    -- 收集所有物品信息
    local items = {}
    for bag = 0, NUM_BAG_SLOTS do
        local slots = GetContainerNumSlots(bag)
        for slot = 1, slots do
            local itemID = GetContainerItemID(bag, slot)
            if itemID then
                local itemName, _, rarity, _, _, itemType, itemSubType = GetItemInfo(itemID)
                local _, count = GetContainerItemInfo(bag, slot)
                items[#items + 1] = {
                    bag = bag,
                    slot = slot,
                    itemID = itemID,
                    name = itemName,
                    rarity = rarity,
                    type = itemType,
                    subType = itemSubType,
                    count = count,
                }
            end
        end
    end

    -- 按品质、类型、名称排序
    table.sort(items, function(a, b)
        -- 先按品质降序
        if a.rarity ~= b.rarity then
            return a.rarity > b.rarity
        end
        -- 再按类型
        if a.type ~= b.type then
            return a.type < b.type
        end
        -- 最后按名称
        return a.name < b.name
    end)

    print("背包整理完成,共 " .. #items .. " 个物品")
end

-- 注册斜杠命令
SLASH_BAGSORT1 = "/bagsort"
SlashCmdList["BAGSORT"] = function(msg)
    BagSorter.Sort()
end

注意事项与常见错误

不要使用阻塞操作。WoW 的 Lua 环境运行在主线程中,任何阻塞操作都会导致游戏界面卡死。WoW 已经移除了 os.execute、io.open 等可能阻塞的 API。所有需要延迟执行的操作应使用 C_Timer.After 或 OnUpdate 脚本实现。

注意 API 版本兼容性。WoW 每个版本都可能修改或废弃部分 API。在 TOC 文件中正确设置 Interface 版本号,并使用 Interface/AddOns 中的加载机制检查兼容性开发建议参考当前版本的 API 文档,避免使用已废弃的函数。

SavedVariables 的加载时机。SavedVariables 在 ADDON_LOADED 事件触发时才可用,在此之前访问会得到 nil。如果插件需要在初始化读取保存的配置,务必在 ADDON_LOADED 事件执行初始化逻辑

避免在 OnUpdate 中执行计算。OnUpdate 每帧都会触发(通常约 60 次/秒),在其中执行复杂计算会严重影响游戏帧。应使用节流机制(如累加 elapsed 时间,达到阈执行)来降低执行

字符串拼接的性能问题。在频繁更新文本(如 DPS 计时器)中,避免每帧都重新拼接字符串。可以使用 FontString 的 SetFormattedText 方法,或者在数据变化时才更新显示。

高级用法

使用 Ace3 框架

Ace3 是 WoW 插件开发中最流行框架提供了丰富的工具库:

-- 使用 Ace3 创建插件
local addon = LibStub("AceAddon-3.0"):NewAddon("MyAceAddon", "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0")

-- 插件初始化
function addon:OnInitialize()
    -- 初始化数据库(自动处理 SavedVariables)
    self.db = LibStub("AceDB-3.0"):New("MyAceAddonDB", {
        profile = {
            enabled = true,
            showPanel = true,
            alertThreshold = 30,
        },
    })

    -- 注册斜杠命令
    self:RegisterChatCommand("maa", "ChatCommand")
    self:RegisterChatCommand("myaceaddon", "ChatCommand")

    print("MyAceAddon 已初始化")
end

-- 插件启用
function addon:OnEnable()
    -- 注册事件
    self:RegisterEvent("PLAYER_ENTERING_WORLD", "OnEnteringWorld")
    self:RegisterEvent("UNIT_HEALTH", "OnHealthChanged")

    -- 使用 AceTimer 定时执行
    self:ScheduleRepeatingTimer("UpdateInfo", 5)

    print("MyAceAddon 已启用")
end

-- 插件禁用
function addon:OnDisable()
    self:CancelAllTimers()
    self:UnregisterAllEvents()
end

-- 事件处理
function addon:OnEnteringWorld(event, isLogin)
    if isLogin then
        self:Print("欢迎回来!")
    end
end

function addon:OnHealthChanged(event, unit)
    if unit == "player" then
        local health = UnitHealth("player")
        local maxHealth = UnitHealthMax("player")
        local percent = (health / maxHealth) * 100

        if percent < self.db.profile.alertThreshold then
            self:Print("警告:生命值低于 " .. self.db.profile.alertThreshold .. "%!")
        end
    end
end

-- 定时更新
function addon:UpdateInfo()
    -- 定期执行的任务
end

-- 斜杠命令处理
function addon:ChatCommand(msg)
    local cmd = msg:lower():trim()
    if cmd == "show" then
        self.db.profile.showPanel = true
    elseif cmd == "hide" then
        self.db.profile.showPanel = false
    elseif cmd == "reset" then
        self.db:ResetProfile()
        self:Print("配置已重置")
    else
        self:Print("命令列表: show, hide, reset")
    end
end

创建小地图按钮

插件添加小地图图标,方便快速访问:

-- 创建小地图按钮
local minimapButton = CreateFrame("Button", "MyAddonMinimapButton", Minimap)
minimapButton:SetSize(32, 32)
minimapButton:SetNormalTexture("Interface\\Icons\\INV_Misc_QuestionMark")
minimapButton:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
minimapButton:SetPushedTexture("Interface\\Icons\\INV_Misc_QuestionMark")

-- 设置小地图位置(角度)
local function UpdateMinimapButtonPosition()
    local angle = MyAddonDB.minimapAngle or 45
    local rad = math.rad(angle)
    local x = math.cos(rad) * 80
    local y = math.sin(rad) * 80
    minimapButton:SetPoint("CENTER", Minimap, "CENTER", x, y)
end

-- 拖动小地图按钮
minimapButton:SetMovable(true)
minimapButton:EnableMouse(true)
minimapButton:RegisterForDrag("LeftButton")

minimapButton:SetScript("OnDragStart", function(self)
    self:StartMoving()
    self.isDragging = true
end)

minimapButton:SetScript("OnDragStop", function(self)
    self:StopMovingOrSizing()
    self.isDragging = false

    -- 计算按钮相对于小地图中心的角度
    local cx, cy = Minimap:GetCenter()
    local mx, my = self:GetCenter()
    local angle = math.deg(math.atan2(my - cy, mx - cx))
    MyAddonDB.minimapAngle = angle
end)

-- 点击小地图按钮
minimapButton:SetScript("OnClick", function(self, button)
    if button == "LeftButton" then
        -- 左键点击:切换面板显示
        if info_frame:IsShown() then
            info_frame:Hide()
        else
            info_frame:Show()
        end
    elseif button == "RightButton" then
        -- 右键点击:显示设置菜单
        local menu = {
            { text = "MyAddon 设置", isTitle = true },
            { text = "显示面板", checked = MyAddonDB.showPanel, func = function()
                MyAddonDB.showPanel = not MyAddonDB.showPanel
            end },
            { text = "低血量警报", checked = MyAddonDB.alerts.lowHealth, func = function()
                MyAddonDB.alerts.lowHealth = not MyAddonDB.alerts.lowHealth
            end },
        }
        EasyMenu(menu, CreateFrame("Frame", nil, UIParent, "UIDropDownMenuTemplate"), "cursor", 0, 0, "MENU")
    end
end)

UpdateMinimapButtonPosition()

消息过滤与聊天增强

实现聊天消息过滤和增强功能:

-- 聊天消息过滤器
local chatFilter = CreateFrame("Frame")
chatFilter.filters = {
    -- 过滤关键词列表
    keywords = {"金币", "代练", "工作室"},
    -- 是否启用过滤
    enabled = true,
}

-- 注册聊天过滤器
ChatFrame_AddMessageEventFilter("CHAT_MSG_CHANNEL", function(self, event, msg, author, ...)
    if not chatFilter.filters.enabled then
        return false  -- 不过滤
    end

    -- 检查关键词
    local lowerMsg = msg:lower()
    for _, keyword in ipairs(chatFilter.filters.keywords) do
        if lowerMsg:find(keyword:lower()) then
            return true  -- 过滤掉该消息
        end
    end

    return false  -- 保留该消息
end)

-- 聊天消息高亮
ChatFrame_AddMessageEventFilter("CHAT_MSG_WHISPER", function(self, event, msg, author, ...)
    -- 为密语消息添加高亮前缀
    return false, "|cFFFF9900[密语]|r " .. msg, author, ...
end)

-- 注册斜杠命令管理过滤器
SLASH_CHATFILTER1 = "/cf"
SlashCmdList["CHATFILTER"] = function(msg)
    local cmd, arg = msg:match("^(%S*)%s*(.-)$")

    if cmd == "on" then
        chatFilter.filters.enabled = true
        print("聊天过滤器已启用")
    elseif cmd == "off" then
        chatFilter.filters.enabled = false
        print("聊天过滤器已禁用")
    elseif cmd == "add" then
        if arg and #arg > 0 then
            chatFilter.filters.keywords[#chatFilter.filters.keywords + 1] = arg
            print("已添加过滤关键词: " .. arg)
        end
    elseif cmd == "list" then
        print("当前过滤关键词:")
        for i, kw in ipairs(chatFilter.filters.keywords) do
            print("  " .. i .. ". " .. kw)
        end
    else
        print("用法: /cf on|off|add <keyword>|list")
    end
end

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式